"""
导入导出验证器：测试导入导出功能是否正常工作
"""
from __future__ import annotations

import sys
from pathlib import Path
from typing import List, Tuple, Dict, Set

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

from .domain import (
    ProjectSnapshot,
    ProjectMetadata,
    CategorySnapshot,
    ParameterSnapshot,
    ParameterValueSnapshot,
)
from .repository import ProjectRepository
from .enums import IOFormat


class ValidationError(Exception):
    """验证失败"""


def _build_ignore_groups(ignore_field_paths: List[str] | None) -> Dict[str, Set[str]]:
    groups: Dict[str, Set[str]] = {
        "metadata": {"export_time", "checksum"},
        "category": set(),
        "parameter": set(),
        "value": set(),
    }

    if not ignore_field_paths:
        return groups

    for path in ignore_field_paths:
        if not path or "." not in path:
            continue
        group, field = path.split(".", 1)
        if field and group in groups:
            groups[group].add(field)

    return groups


def compare_snapshots(
    snapshot1: ProjectSnapshot,
    snapshot2: ProjectSnapshot,
    ignore_field_paths: List[str] | None = None,
) -> Tuple[bool, List[str]]:
    """
    比较两个项目快照是否相同。

    Args:
        snapshot1: 第一个快照
        snapshot2: 第二个快照
        ignore_field_paths: 要忽略的字段路径列表（如 "metadata.export_time", "parameter.description"）

    Returns:
        (是否相同, 差异列表)
    """
    ignore_groups = _build_ignore_groups(ignore_field_paths)

    differences: List[str] = []

    # 比较元数据（忽略指定字段和项目名称，因为导入时会创建副本）
    meta1 = snapshot1.metadata
    meta2 = snapshot2.metadata

    # 忽略项目名称（name, name_en），因为导入时会自动添加副本后缀
    for field in ["description", "time_horizon", "start_year", "year_step", "end_year"]:
        if field in ignore_groups["metadata"]:
            continue
        val1 = getattr(meta1, field)
        val2 = getattr(meta2, field)
        if val1 != val2:
            differences.append(f"元数据.{field}: {val1} != {val2}")

    # 比较分类数量
    if len(snapshot1.categories) != len(snapshot2.categories):
        differences.append(
            f"分类数量不同: {len(snapshot1.categories)} != {len(snapshot2.categories)}"
        )
        return False, differences

    # 按name_en排序后比较
    cats1 = sorted(snapshot1.categories, key=lambda c: c.name_en)
    cats2 = sorted(snapshot2.categories, key=lambda c: c.name_en)

    for i, (cat1, cat2) in enumerate(zip(cats1, cats2)):
        cat_diff = _compare_categories(cat1, cat2, ignore_groups, f"分类[{i}]")
        differences.extend(cat_diff)

    return len(differences) == 0, differences


def _compare_categories(
    cat1: CategorySnapshot,
    cat2: CategorySnapshot,
    ignore_groups: Dict[str, Set[str]],
    prefix: str = "",
) -> List[str]:
    """比较两个分类"""
    differences: List[str] = []
    category_ignores = ignore_groups["category"]

    if "name" not in category_ignores and cat1.name != cat2.name:
        differences.append(f"{prefix}.name: {cat1.name} != {cat2.name}")
    if "name_en" not in category_ignores and cat1.name_en != cat2.name_en:
        differences.append(f"{prefix}.name_en: {cat1.name_en} != {cat2.name_en}")
    if "description" not in category_ignores:
        # 将空字符串和 None 视为相同
        desc1 = cat1.description or None
        desc2 = cat2.description or None
        if desc1 != desc2:
            differences.append(
                f"{prefix}.description: {cat1.description} != {cat2.description}"
            )
    if "sort_order" not in category_ignores and cat1.sort_order != cat2.sort_order:
        differences.append(
            f"{prefix}.sort_order: {cat1.sort_order} != {cat2.sort_order}"
        )

    if len(cat1.parameters) != len(cat2.parameters):
        differences.append(
            f"{prefix}.参数数量: {len(cat1.parameters)} != {len(cat2.parameters)}"
        )
        return differences

    # 按name_en排序后比较
    params1 = sorted(cat1.parameters, key=lambda p: p.name_en)
    params2 = sorted(cat2.parameters, key=lambda p: p.name_en)

    for i, (param1, param2) in enumerate(zip(params1, params2)):
        param_diff = _compare_parameters(param1, param2, ignore_groups, f"{prefix}.参数[{i}]")
        differences.extend(param_diff)

    return differences


def _compare_parameters(
    param1: ParameterSnapshot,
    param2: ParameterSnapshot,
    ignore_groups: Dict[str, Set[str]],
    prefix: str = "",
) -> List[str]:
    """比较两个参数"""
    differences: List[str] = []
    parameter_ignores = ignore_groups["parameter"]
    value_ignores = ignore_groups["value"]

    fields = [
        "name",
        "name_en",
        "param_type",
        "unit",
        "description",
        "is_list",
        "is_year_related",
        "list_length",
        "sort_order",
    ]
    for field in fields:
        if field in parameter_ignores:
            continue
        val1 = getattr(param1, field)
        val2 = getattr(param2, field)
        # 对于 description 字段，将空字符串和 None 视为相同
        if field == "description":
            val1 = val1 or None
            val2 = val2 or None
        if val1 != val2:
            differences.append(f"{prefix}.{field}: {val1} != {val2}")

    if "values" in parameter_ignores:
        return differences

    # 比较值
    if len(param1.values) != len(param2.values):
        differences.append(
            f"{prefix}.值数量: {len(param1.values)} != {len(param2.values)}"
        )
        return differences

    # 按index排序后比较
    values1 = sorted(param1.values, key=lambda v: (v.index is None, v.index or 0))
    values2 = sorted(param2.values, key=lambda v: (v.index is None, v.index or 0))

    for i, (val1, val2) in enumerate(zip(values1, values2)):
        if "index" not in value_ignores and val1.index != val2.index:
            differences.append(
                f"{prefix}.值[{i}].index: {val1.index} != {val2.index}"
            )
        if "value" not in value_ignores and val1.value != val2.value:
            differences.append(
                f"{prefix}.值[{i}].value: {val1.value} != {val2.value}"
            )

    return differences


def validate_single_project_format(
    db: Session,
    repository: ProjectRepository,
    project_name_en: str,
    format_type: IOFormat,
    output_dir: Path,
    verbose: bool = True,
) -> Tuple[bool, List[str], str]:
    """
    验证单个项目的单个格式的导入导出功能。
    
    流程：
    1. 从数据库读取项目 -> snapshot1
    2. 导出策略将 snapshot1 导出为文件
    3. 导入策略解析文件 -> snapshot2
    4. 比较 snapshot1 和 snapshot2

    Args:
        db: 数据库会话（仅用于读取）
        repository: 项目仓储
        project_name_en: 项目英文名称
        format_type: 导入导出格式
        output_dir: 导出文件目录
        verbose: 是否输出详细信息

    Returns:
        (是否通过, 差异列表, 导出文件路径)
    """
    from .services.exporter import EXPORT_STRATEGIES
    from .services.importer import IMPORT_STRATEGIES
    
    try:
        # 1. 从数据库读取项目 -> snapshot1
        snapshot1 = repository.load_by_name_en(project_name_en)
        if verbose:
            print(f"  ✓ 读取项目 '{project_name_en}' 成功")
            print(f"    - 分类数: {len(snapshot1.categories)}")
            total_params = sum(len(cat.parameters) for cat in snapshot1.categories)
            print(f"    - 参数数: {total_params}")

        # 2. 导出策略将 snapshot1 导出为文件
        export_strategy_cls = EXPORT_STRATEGIES.get(format_type)
        if not export_strategy_cls:
            raise ValueError(f"不支持的导出格式: {format_type.value}")
        
        if format_type in (IOFormat.JSON, IOFormat.EXCEL):
            export_strategy = export_strategy_cls(output_dir=output_dir)
        else:
            export_strategy = export_strategy_cls()
        
        export_result = export_strategy.export(snapshot1)
        export_file = export_result.file_path
        
        if verbose:
            print(f"  ✓ 导出成功: {export_file.name}")

        # 3. 导入策略解析文件 -> snapshot2
        import_strategy_cls = IMPORT_STRATEGIES.get(format_type)
        if not import_strategy_cls:
            raise ValueError(f"不支持的导入格式: {format_type.value}")
        
        import_strategy = import_strategy_cls()
        snapshot2 = import_strategy.import_snapshot(export_file)
        
        if verbose:
            print(f"  ✓ 导入策略解析文件成功")
            print(f"    - 分类数: {len(snapshot2.categories)}")
            total_params2 = sum(len(cat.parameters) for cat in snapshot2.categories)
            print(f"    - 参数数: {total_params2}")

        # 4. 比较 snapshot1 和 snapshot2
        export_ignore_fields = export_strategy.get_diff_ignore_fields()
        import_ignore_fields = import_strategy.get_diff_ignore_fields()
        combined_ignore_fields = list(
            dict.fromkeys(export_ignore_fields + import_ignore_fields)
        )
        is_same, differences = compare_snapshots(
            snapshot1, snapshot2, ignore_field_paths=combined_ignore_fields
        )

        if verbose:
            if is_same:
                print("  ✓ 验证通过：两个快照完全相同")
            else:
                print("  ✗ 验证失败：发现以下差异：")
                for diff in differences:
                    print(f"    - {diff}")

        return is_same, differences, str(export_file)

    except Exception as e:
        if verbose:
            print(f"  ✗ 验证过程出错: {e}")
        raise


def get_available_formats() -> List[IOFormat]:
    """获取所有同时支持导入和导出的格式"""
    from .services.exporter import EXPORT_STRATEGIES
    from .services.importer import IMPORT_STRATEGIES
    
    export_formats = set(EXPORT_STRATEGIES.keys())
    import_formats = set(IMPORT_STRATEGIES.keys())
    
    # 只返回同时支持导入和导出的格式
    available = list(export_formats & import_formats)
    return sorted(available, key=lambda f: f.value)


def validate_all_projects_formats(
    db_path: str,
    output_dir: Path | None = None,
    verbose: bool = True,
) -> Tuple[bool, Dict[str, Dict[str, Tuple[bool, List[str]]]]]:
    """
    验证数据库中所有项目的所有导入导出格式。

    Args:
        db_path: 数据库路径
        output_dir: 导出文件目录（默认使用临时目录）
        verbose: 是否输出详细信息

    Returns:
        (总体是否通过, 详细结果字典 {项目名: {格式: (是否通过, 差异列表)}})
    """
    import tempfile
    from ..db.parameters import crud as parameter_crud

    if output_dir is None:
        output_dir = Path(tempfile.mkdtemp())

    # 连接数据库
    engine = create_engine(f"sqlite:///{db_path}")
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    db = SessionLocal()

    try:
        # 获取所有项目
        projects = parameter_crud.get_projects(db, skip=0, limit=1000)
        if not projects:
            print("数据库中没有项目")
            return True, {}

        # 获取所有可用格式
        available_formats = get_available_formats()
        if not available_formats:
            print("没有可用的导入导出格式")
            return True, {}

        repository = ProjectRepository(db)
        results: Dict[str, Dict[str, Tuple[bool, List[str]]]] = {}
        all_passed = True

        if verbose:
            print(f"找到 {len(projects)} 个项目")
            print(f"支持的格式: {', '.join(f.value for f in available_formats)}")
            print("=" * 60)

        # 对每个项目、每个格式进行验证
        for project in projects:
            project_name_en = project.name_en
            results[project_name_en] = {}

            if verbose:
                print(f"\n验证项目: {project.name} ({project_name_en})")
                print("-" * 60)

            for format_type in available_formats:
                if verbose:
                    print(f"\n格式: {format_type.value}")

                try:
                    passed, differences, _ = validate_single_project_format(
                        db, repository, project_name_en, format_type, output_dir, verbose
                    )
                    results[project_name_en][format_type.value] = (passed, differences)
                    if not passed:
                        all_passed = False
                except Exception as e:
                    if verbose:
                        print(f"  ✗ 验证失败: {e}")
                    results[project_name_en][format_type.value] = (False, [str(e)])
                    all_passed = False

        return all_passed, results

    finally:
        db.close()


def print_summary(all_passed: bool, results: Dict[str, Dict[str, Tuple[bool, List[str]]]]):
    """打印验证结果汇总"""
    print("\n" + "=" * 60)
    print("验证结果汇总")
    print("=" * 60)
    
    total_tests = 0
    passed_tests = 0
    
    for project_name, format_results in results.items():
        for format_name, (passed, differences) in format_results.items():
            total_tests += 1
            if passed:
                passed_tests += 1
            else:
                print(f"\n✗ {project_name} - {format_name}: 失败")
                for diff in differences[:5]:  # 只显示前5个差异
                    print(f"    - {diff}")
                if len(differences) > 5:
                    print(f"    ... 还有 {len(differences) - 5} 个差异")
    
    print(f"\n总计: {passed_tests}/{total_tests} 通过")
    if all_passed:
        print("✓ 所有验证通过！")
    else:
        print(f"✗ {total_tests - passed_tests} 个验证失败")


def main():
    """命令行入口"""
    if len(sys.argv) < 2:
        print("用法: python validator.py <数据库路径>")
        print("示例: python validator.py tests/param_management.db")
        print("\n验证器会自动：")
        print("  - 读取数据库中的所有项目")
        print("  - 使用所有支持的导入导出格式进行验证")
        sys.exit(1)

    db_path = sys.argv[1]

    if not Path(db_path).exists():
        print(f"错误: 数据库文件不存在: {db_path}")
        sys.exit(1)

    print("=" * 60)
    print("导入导出验证器")
    print("=" * 60)
    print(f"数据库: {db_path}")
    print("开始验证所有项目和所有格式...")
    print()

    try:
        all_passed, results = validate_all_projects_formats(db_path, verbose=True)
        print_summary(all_passed, results)
        
        if all_passed:
            sys.exit(0)
        else:
            sys.exit(1)
    except Exception as e:
        print(f"\n✗ 验证过程出错: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)


if __name__ == "__main__":
    main()

